package com.dapeng.cleardatabackend.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dapeng.cleardatabackend.annotation.MultiTransactional;
import com.dapeng.cleardatabackend.common.ErrorCode;
import com.dapeng.cleardatabackend.common.LoginUserHolder;
import com.dapeng.cleardatabackend.exception.BusinessException;
import com.dapeng.cleardatabackend.exception.ThrowUtils;
import com.dapeng.cleardatabackend.mapper.RelationMapper;
import com.dapeng.cleardatabackend.model.dto.relation.RelationAddRequest;
import com.dapeng.cleardatabackend.model.dto.relation.RelationDeleteRequest;
import com.dapeng.cleardatabackend.model.dto.relation.RelationGetRequest;
import com.dapeng.cleardatabackend.model.dto.relation.RelationUpdateRequest;
import com.dapeng.cleardatabackend.model.entity.Graph;
import com.dapeng.cleardatabackend.model.entity.Node;
import com.dapeng.cleardatabackend.model.entity.Relation;
import com.dapeng.cleardatabackend.model.entity.User;
import com.dapeng.cleardatabackend.model.vo.RelationVO;
import com.dapeng.cleardatabackend.service.GraphService;
import com.dapeng.cleardatabackend.service.NodeService;
import com.dapeng.cleardatabackend.service.RelationService;
import com.dapeng.cleardatabackend.utils.CsvUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author dapeng
 * @description 针对表【relation(关系表)】的数据库操作Service实现
 * @createDate 2024-12-22 13:29:10
 */
@Service
public class RelationServiceImpl extends ServiceImpl<RelationMapper, Relation>
        implements RelationService {

    @Resource
    private Neo4jClient neo4jClient;

    @Resource
    @Lazy
    private GraphService graphService;

    @Resource
    @Lazy
    private NodeService nodeService;

    @Override
    public void saveRelationToNeo4j(List<Relation> relationList, Long graphId) {
        List<Map<String, String>> relations = relationList.stream().map(relation -> {
            HashMap<String, String> map = new HashMap<>();
            map.put("startNodeId", relation.getStartNodeId());
            map.put("relationText", relation.getRelationText());
            map.put("endNodeId", relation.getEndNodeId());
            return map;
        }).collect(Collectors.toList());
        HashMap<String, Object> params = new HashMap<>();
        params.put("relations", relations);
        String query = String.format(
                "CALL apoc.periodic.iterate(" +
                        "'UNWIND $relations AS rel RETURN rel', " +
                        "'MATCH (start:`%s` {nodeId: rel.startNodeId}) " +
                        "MATCH (end:`%s` {nodeId: rel.endNodeId}) " +
                        "WITH start, end, rel " +
                        "OPTIONAL MATCH (start)-[r:`%s` {relation: rel.relationText}]->(end) " +
                        "WHERE r IS NULL " +
                        "CREATE (start)-[:`%s` {relation: rel.relationText}]->(end)', " +
                        "{batchSize: 2000, parallel: true, concurrency: 8, params: {relations: $relations}})",
                graphId, graphId, graphId, graphId
        );
        neo4jClient
                .query(query)
                .bindAll(params)
                .run();
    }

    @Override
    @MultiTransactional
    public Long addSingleRelation(RelationAddRequest relationAddRequest) {
        Long graphId = relationAddRequest.getGraphId();
        Long startId = relationAddRequest.getStartId();
        String relationText = relationAddRequest.getRelationText();
        Long endId = relationAddRequest.getEndId();
        Graph graph = graphService.getById(graphId);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        List<Node> nodes = nodeService.listByIds(Arrays.asList(startId, endId));
        ThrowUtils.throwIf(CollectionUtils.isEmpty(nodes), ErrorCode.PARAMS_ERROR, "节点不存在");
        Node startNode;
        Node endNode;
        if (nodes.size() == 1) {
            startNode = nodes.get(0);
            endNode = startNode;
        } else {
            startNode = nodes.get(0).getId().equals(startId) ? nodes.get(0) : nodes.get(1);
            endNode = nodes.get(0).getId().equals(startId) ? nodes.get(1) : nodes.get(0);
        }
        LambdaQueryWrapper<Relation> queryWrapper = new LambdaQueryWrapper<>();
        String startNodeNodeId = startNode.getNodeId();
        String endNodeNodeId = endNode.getNodeId();
        queryWrapper.eq(Relation::getGraphId, graphId).eq(Relation::getStartNodeId, startNodeNodeId)
                .eq(Relation::getRelationText, relationText).eq(Relation::getEndNodeId, endNodeNodeId);
        boolean existed = baseMapper.exists(queryWrapper);
        ThrowUtils.throwIf(existed, ErrorCode.PARAMS_ERROR, "该关系已存在");
        Relation relation = new Relation();
        relation.setGraphId(graphId);
        relation.setStartNodeId(startNodeNodeId);
        relation.setRelationText(relationText);
        relation.setEndNodeId(endNodeNodeId);
        baseMapper.insert(relation);
        HashMap<String, Object> params = new HashMap<>();
        params.put("startNodeNodeId", startNodeNodeId);
        params.put("relationText", relationText);
        params.put("endNodeNodeId", endNodeNodeId);
        String query = String.format(
                "MATCH (start:`%s` {nodeId: $startNodeNodeId}) " +
                        "MATCH (end:`%s` {nodeId: $endNodeNodeId}) " +
                        "WITH start, end " +
                        "OPTIONAL MATCH (start)-[r:`%s` {relation: $relationText}]->(end) " +
                        "WHERE r IS NULL " +
                        "CREATE (start)-[:`%s` {relation: $relationText}]->(end) " +
                        "RETURN 'success' AS status",
                graphId, graphId, graphId, graphId
        );
        Optional<Map<String, Object>> result = neo4jClient
                .query(query)
                .bindAll(params)
                .fetch().one();
        ThrowUtils.throwIf(!result.isPresent(), ErrorCode.OPERATION_ERROR, "关系添加失败");
        return relation.getId();
    }

    @Override
    public void hardRemoveByGraphId(Long graphId) {
        baseMapper.hardDeleteByGraphId(graphId);
    }

    @Override
    public Page<RelationVO> getRelationPage(RelationGetRequest relationGetRequest) {
        Long graphId = relationGetRequest.getGraphId();
        String startNodeText = relationGetRequest.getStartNodeText();
        String relationText = relationGetRequest.getRelationText();
        String endNodeText = relationGetRequest.getEndNodeText();
        int current = relationGetRequest.getCurrent();
        int pageSize = relationGetRequest.getPageSize();
        Graph graph = graphService.getById(graphId);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Page<Relation> page = new Page<>(current, pageSize);
        return baseMapper.getRelationPage(page, graphId, startNodeText, relationText, endNodeText);
    }

    @Override
    public Page<RelationVO> getOriginalRelationPage(RelationGetRequest relationGetRequest) {
        int current = relationGetRequest.getCurrent();
        int pageSize = relationGetRequest.getPageSize();
        Long graphId = relationGetRequest.getGraphId();
        String startNodeId = relationGetRequest.getStartNodeId();
        String relationText = relationGetRequest.getRelationText();
        String endNodeId = relationGetRequest.getEndNodeId();
        Graph graph = graphService.getById(graphId);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Page<Relation> page = new Page<>(current, pageSize);
        LambdaQueryWrapper<Relation> queryWrapper = new LambdaQueryWrapper<Relation>().eq(Relation::getGraphId, graphId)
                .like(!StrUtil.isBlankIfStr(relationText), Relation::getRelationText, relationText)
                .like(!StrUtil.isBlankIfStr(startNodeId), Relation::getStartNodeId, startNodeId)
                .like(!StrUtil.isBlankIfStr(endNodeId), Relation::getEndNodeId, endNodeId);
        Page<Relation> relationPage = page(page, queryWrapper);
        Page<RelationVO> relationVoPage = new Page<>(relationPage.getCurrent(), relationPage.getSize(), relationPage.getTotal());
        relationVoPage.setRecords(relationPage.getRecords().stream().map(RelationVO::beanToVo).collect(Collectors.toList()));
        return relationVoPage;
    }

    @Override
    @MultiTransactional
    public Boolean updateRelation(RelationUpdateRequest relationUpdateRequest) {
        Long graphId = relationUpdateRequest.getGraphId();
        Long id = relationUpdateRequest.getId();
        String newRelationText = relationUpdateRequest.getRelationText();
        Graph graph = graphService.getById(graphId);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Relation relation = getById(id);
        ThrowUtils.throwIf(relation == null, ErrorCode.OPERATION_ERROR, "关系不存在");
        String startNodeId = relation.getStartNodeId();
        String oldRelationText = relation.getRelationText();
        String endNodeId = relation.getEndNodeId();
        HashMap<String, Object> params = new HashMap<>();
        params.put("startNodeId", startNodeId);
        params.put("oldRelationText", oldRelationText);
        params.put("endNodeId", endNodeId);
        params.put("newRelationText", newRelationText);
        String query = String.format(
                "MATCH (a:`%s` {nodeId: $startNodeId})-[r:`%s` {relation: $oldRelationText}]->(b:`%s` {nodeId: $endNodeId}) " +
                        "SET r.relation = $newRelationText " +
                        "RETURN 'success' AS status",
                graphId, graphId, graphId
        );
        Optional<Map<String, Object>> result = neo4jClient
                .query(query)
                .bindAll(params)
                .fetch()
                .one();
        ThrowUtils.throwIf(!result.isPresent(), ErrorCode.OPERATION_ERROR, "关系更新失败");
        Relation newRelation = new Relation();
        newRelation.setId(id);
        newRelation.setRelationText(newRelationText);
        return updateById(newRelation);
    }

    @Override
    @MultiTransactional
    public Boolean deleteRelation(RelationDeleteRequest relationDeleteRequest) {
        Long graphId = relationDeleteRequest.getGraphId();
        Long id = relationDeleteRequest.getId();
        Graph graph = graphService.getById(graphId);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Relation relation = getById(id);
        String startNodeId = relation.getStartNodeId();
        String relationText = relation.getRelationText();
        String endNodeId = relation.getEndNodeId();
        HashMap<String, Object> params = new HashMap<>();
        params.put("startNodeId", startNodeId);
        params.put("relationText", relationText);
        params.put("endNodeId", endNodeId);
        String query = String.format(
                "MATCH (a:`%s` {nodeId: $startNodeId})-[rel {relation: $relationText}]->(b:`%s` {nodeId: $endNodeId}) " +
                        "WITH rel " +
                        "DELETE rel " +
                        "RETURN 'success' AS status",
                graphId, graphId
        );
        Optional<Map<String, Object>> result = neo4jClient
                .query(query)
                .bindAll(params)
                .fetch()
                .one();
        ThrowUtils.throwIf(!result.isPresent(), ErrorCode.OPERATION_ERROR, "删除关系失败");
        boolean remove = this.removeById(id);
        ThrowUtils.throwIf(!remove, ErrorCode.OPERATION_ERROR, "删除关系失败");
        return true;
    }

    @Override
    @MultiTransactional
    public Boolean deleteRelationByGraphId(Long graphId) {
        remove(new LambdaQueryWrapper<Relation>().eq(Relation::getGraphId, graphId));
        String query = String.format("MATCH (a:`%s`)-[r]->(b:`%s`) DELETE r", graphId, graphId);
        neo4jClient.query(query).run();
        return true;
    }

    @Override
    public ResponseEntity<byte[]> downloadCsv(Long id) {
        Graph graph = graphService.getById(id);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        List<RelationVO> relationList = baseMapper.getRelationList(id);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
        String filename = String.format("%s关系文件.csv", graph.getName());
        try {
            String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
            headers.setContentDispositionFormData("attachment", encodedFilename);
        } catch (UnsupportedEncodingException e) {
            headers.setContentDispositionFormData("attachment", filename);
        }
        StringWriter stringWriter = new StringWriter();
        CSVFormat csvFormat = CsvUtils.getCsvFormat(",");
        try (CSVPrinter csvPrinter = new CSVPrinter(stringWriter, csvFormat)) {
            for (RelationVO relationVO : relationList) {
                csvPrinter.printRecord(relationVO.getStartNodeText(), relationVO.getRelationText(), relationVO.getEndNodeText());
            }
        } catch (IOException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成文件失败");
        }
        byte[] fileContent = stringWriter.toString().getBytes(StandardCharsets.UTF_8);
        return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
    }

    @Override
    public ResponseEntity<byte[]> downloadOriginalCsv(Long id) {
        Graph graph = graphService.getById(id);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        List<Relation> relationList = baseMapper.selectList(new LambdaQueryWrapper<Relation>().eq(Relation::getGraphId, id));
        ThrowUtils.throwIf(CollectionUtils.isEmpty(relationList), ErrorCode.OPERATION_ERROR, "图谱关系为空");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
        String filename = String.format("%s关系文件.csv", graph.getName());
        try {
            String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
            headers.setContentDispositionFormData("attachment", encodedFilename);
        } catch (UnsupportedEncodingException e) {
            headers.setContentDispositionFormData("attachment", filename);
        }
        StringWriter stringWriter = new StringWriter();
        CSVFormat csvFormat = CsvUtils.getCsvFormat(",");
        try (CSVPrinter csvPrinter = new CSVPrinter(stringWriter, csvFormat)) {
            for (Relation relation : relationList) {
                csvPrinter.printRecord(relation.getStartNodeId(), relation.getRelationText(), relation.getEndNodeId());
            }
        } catch (IOException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成文件失败");
        }
        byte[] fileContent = stringWriter.toString().getBytes(StandardCharsets.UTF_8);
        return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
    }
}





